#!/usr/bin/python2.7
# coding=utf-8

import os
import sys
import wx
import Queue
import numpy as np

import vinsch_comm as vs

from vinsch_comm_objects import Parameter, Sample
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
  FigureCanvasWxAgg as FigCanvas, \
  NavigationToolbar2WxAgg as NavigationToolbar

cmd_que = Queue.Queue(1)
log_filename = "./log/record"

class GUIFrame(wx.Frame):
  title = "Vinsch PC"
  PLT_DATA_LEN = 100
  _plt_x_range = np.arange(PLT_DATA_LEN)
  
  def __init__(self):
    os.system('clear')

    # Keep plotted 2D lines here
    self.plot_data = {"Pump rpm":None, "Drum rpm":None, "Temperature":None, "Pressure":None}

    # Graph data
    self.log = DataLog(10000)    
    
    # Create GUI    
    wx.Frame.__init__(self, None, -1, title=self.title)
    self.create_controls()

    # Connect event handlers
    self.Bind(vs.EVT_RESULT_RX, self.on_result_rx)
    self.Bind(vs.EVT_CONNECTION, self.on_connection)    
    self.Bind(wx.EVT_CLOSE, self.on_quit)    

    # Create communication thread     
    self.t = vs.Comm(cmd_que, self)
    self.t.start()

    self.textarea.AppendText("Connecting...\n")

    
  def create_controls(self):

    self.panel = wx.Panel(self)
    self.init_plot()
    self.canvas = FigCanvas(self.panel, -1, self.fig)
    
    self.textarea = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE, size=(-1,150))     
    font = self.textarea.GetFont() 
    font = wx.Font(font.GetPointSize(), wx.TELETYPE,font.GetStyle(),font.GetWeight(), font.GetUnderlined())    
    self.textarea.SetFont(font)    
    
    # Create buttons
    self.sync_button  = wx.Button(self.panel, -1, "sync")
    self.conf_button  = wx.Button(self.panel, -1, "conf")
    self.get_button   = wx.Button(self.panel, -1, "get")
    self.set_button   = wx.Button(self.panel, -1, "set")
    self.up_button    = wx.Button(self.panel, -1, "up")
    self.down_button  = wx.Button(self.panel, -1, "down")
    self.save_button  = wx.Button(self.panel, -1, "save")
    self.load_button  = wx.Button(self.panel, -1, "load")
    self.quit_button  = wx.Button(self.panel, -1, "quit")
    # Bind button events  
    self.Bind(wx.EVT_BUTTON, self.on_sync,  self.sync_button)
    self.Bind(wx.EVT_BUTTON, self.on_conf,  self.conf_button)
    self.Bind(wx.EVT_BUTTON, self.on_get,   self.get_button)
    self.Bind(wx.EVT_BUTTON, self.on_set,   self.set_button)
    self.Bind(wx.EVT_BUTTON, self.on_up,    self.up_button)
    self.Bind(wx.EVT_BUTTON, self.on_down,  self.down_button)
    self.Bind(wx.EVT_BUTTON, self.on_save,  self.save_button)
    self.Bind(wx.EVT_BUTTON, self.on_load,  self.load_button)
    self.Bind(wx.EVT_BUTTON, self.on_quit,  self.quit_button)
    # Create container for buttons 
    self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)    
    # Add buttons to container
    self.hbox1.Add(self.sync_button,  border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.conf_button,  border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.get_button,   border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.set_button,   border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.up_button,    border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.down_button,  border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.save_button,  border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.load_button,  border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.hbox1.Add(self.quit_button,  border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)

    # Checkboxes
    self.hbox22 = wx.BoxSizer(wx.VERTICAL)
    self.chkbox_pump = wx.CheckBox(self.panel, -1, "Pump rpm")
    self.chkbox_pump.index = 1
    self.chkbox_drum = wx.CheckBox(self.panel, -1, "Drum rpm")
    self.chkbox_drum.index = 2
    self.chkbox_temp = wx.CheckBox(self.panel, -1, "Temperatur")
    self.chkbox_temp.index = 3
    self.chkbox_pres = wx.CheckBox(self.panel, -1, "Pressure")
    self.chkbox_pres.index = 4
    
    self.Bind(wx.EVT_CHECKBOX, self.on_chkbox_click, self.chkbox_pump)
    self.Bind(wx.EVT_CHECKBOX, self.on_chkbox_click, self.chkbox_pump)
    self.Bind(wx.EVT_CHECKBOX, self.on_chkbox_click, self.chkbox_drum)
    self.Bind(wx.EVT_CHECKBOX, self.on_chkbox_click, self.chkbox_temp)
    self.Bind(wx.EVT_CHECKBOX, self.on_chkbox_click, self.chkbox_pres)
    
    self.hbox22.Add(self.chkbox_pump, border=2)
    self.hbox22.Add(self.chkbox_drum, border=2)
    self.hbox22.Add(self.chkbox_temp, border=2)
    self.hbox22.Add(self.chkbox_pres, border=2)


    # Container for canvas and checkboxes    
    self.hbox2 = wx.BoxSizer(wx.HORIZONTAL)
    self.hbox2.Add(self.canvas, border=2, flag=wx.ALL | wx.EXPAND)
    self.hbox2.Add(self.hbox22, border=2, flag=wx.ALL)

    
    # Slider
    self.slider = wx.Slider(self.panel, -1, 0, 0, 100, wx.DefaultPosition, (400, -1), wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS)
    self.slider.SetLineSize(25)
    self.Bind(wx.EVT_SCROLL_CHANGED, self.on_slide,  self.slider)

    
    # Disable all buttons untill connected
    for b in self.hbox1.GetChildren():
      b.GetWindow().Enable(False) 


    self.vbox = wx.BoxSizer(wx.VERTICAL)
    self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)
    self.vbox.Add(self.hbox2, 0, flag=wx.LEFT | wx.TOP | wx.GROW)    
    self.vbox.Add(self.slider, border=2, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
    self.vbox.Add(self.textarea, 1, flag=wx.EXPAND)

    self.panel.SetSizer(self.vbox)
    self.vbox.Fit(self)


  def init_plot(self):
    self.dpi = 100
    self.fig = Figure((5.0, 3.0), dpi=self.dpi)
#    self.fig = Figure()

    self.axes = self.fig.add_subplot(1,1,1)
    self.axes.set_axis_bgcolor('black')
    self.axes.set_title('speed & temp', size=12)
    self.axes.set_xlim(left=0, right=GUIFrame.PLT_DATA_LEN)
    self.axes.set_ylim(bottom=0, top=800)
    
    # plot the data as a line series, and save the reference 
    # to the plotted line series
    
#    self.plot_data["Pump rpm"] = self.axes.plot(GUIFrame._plt_x_range, self.log.data[0:100, 1], linewidth=1)[0]
#    self.plot_data["Drum rpm"] = self.axes.plot(GUIFrame._plt_x_range, self.log.data[0:100, 2], linewidth=1)[0]
#    self.plot_data["Temperature"] = self.axes.plot(GUIFrame._plt_x_range, self.log.data[0:100, 3], linewidth=1)[0]
#    self.plot_data["Pressure"] = self.axes.plot(GUIFrame._plt_x_range, self.log.data[0:100, 4], linewidth=1)[0]


  def draw_plot(self, offset=0):
    """ Redraws the plot """
    # for ymin and ymax, find the minimal and maximal values
    # in the data set and add a mininal margin.
    # 
    # note that it's easy to change this scheme to the 
    # minimal/maximal value in the current display, and not
    # the whole data set.
    # 

#    ymax = max(self.log.data[0:100, 2:3])  
#    self.axes.set_ybound(upper=ymax)
#    offset_max = max(0, self.log.length-100)
#    offset = min(offset_max, max(0, offset))
    view_range =  range(offset, 100+offset)
            
    
#    self.plot_data['tach_pump'].set_ydata(self.log.data[view_range, 1:2])
#    self.plot_data['tach_drum'].set_ydata(self.log.data[view_range, 2:3])

    self.canvas.draw()


  def send_cmd(self, c):
    try:
      cmd_que.put(c, False)
    except Queue.Full:
      self.textarea.ChangeValue("Command que full")
      pass

  def on_result_rx(self, event):
    if isinstance(event.data, Sample):
      self.log.add(event.data)
      self.textarea.ChangeValue(event.data.__str__())
      self.draw_plot()
      
    elif isinstance(event.data,Parameter):
      self.textarea.ChangeValue(event.data.__str__())
    
    elif (event.data==None):
      self.get_button.SetLabel('get')
      self.log.reset()
      self.textarea.ChangeValue("Received None")

    else:
      self.textarea.ChangeValue("Received type %s\n %s" % (type(event.data), event.data))

  def on_connection(self, event):
    # print "on connection event..."
    if event.data==vs.Comm.DISCONNECTED:
      self.textarea.AppendText("Disconnected\n")
      for b in self.hbox1.GetChildren():
        b.GetWindow().Enable(False) 
    elif event.data==vs.Comm.CONNECTED:
      self.textarea.AppendText("Connected\n")
      for b in self.hbox1.GetChildren():
        b.GetWindow().Enable(True) 
    elif event.data==vs.Comm.ERR_CONNECTION:
      if event.errno==77:
        self.textarea.AppendText("Connction failed. Can't find host. (" + str(event.errno) + ").\n")
      elif event.errno==113:
        self.textarea.AppendText("Connction failed. Bluetooth disabled. (" + str(event.errno) + ").\n")
      else:
        self.textarea.AppendText("Connction failed (" + str(event.errno) + ").\n")
          
  def on_sync(self, event):
    self.send_cmd(vs.CMD._SYNC)

  def on_conf(self, event):
    self.send_cmd(vs.CMD.CONF)

  def on_get(self, event):
    if self.get_button.GetLabel()=='get':     
      self.get_button.SetLabel('stop')
      self.send_cmd(vs.CMD.GET)
    else:
      self.get_button.SetLabel('get')
      #self.log.reset()
      self.send_cmd(None)

  def on_set(self, event):
    self.send_cmd(vs.CMD.SET)

  def on_up(self, event):
    self.send_cmd(vs.CMD.UP)

  def on_down(self, event):
    self.send_cmd(vs.CMD.DOWN)
    
  def on_save(self, event):
    self.log.save(self.t.p_set)
  
  def on_load(self, event):
    self.log.load(2)
    slider_max = max(0, self.log.length-100)
    self.slider.SetMax(slider_max)
    self.slider.SetLineSize(25)
    self.draw_plot()
    
  def on_quit(self, event):
    self.send_cmd(vs.CMD.NOCMD)
    self.t.join()
    self.Destroy()

  def on_slide(self, event):
    self.draw_plot(int(self.slider.GetValue()))

  def on_chkbox_click(self, event):
    cb = event.GetEventObject()
    plot_lbl = event.GetEventObject().GetLabelText()
    print "Checkbox clicked: %s at index %d" % (plot_lbl, event.GetEventObject().index)

    if cb.GetValue():
      # Checkbox ticked
      x = GUIFrame._plt_x_range
      y = self.log.get_by_label(plot_lbl)[0:100]
      line = self.axes.plot(x, y, linewidth=1)[0]
      line.set_label(plot_lbl)
    else:
      self.axes.clear()
      lines = self.axes.get_lines()

    self.draw_plot()    


class DataLog:
  """ Keeps data to plot """
  
  def __init__(self, size):
    self.size = size
    self.reset()
    self.recordno = 0
    self.sample_data = np.array([], dtype=vs.Sample.data_type)
    
    
  def add(self, sample):
    self.length = min(self.length+1, self.size)
    
    # Forward data by one...
    self.data = np.roll(self.data, 1, 0)
    # ...the add new sample data last
    self.data[0, 0] = sample.time / 1000
    self.data[0, 1] = sample.tach_pump
    self.data[0, 2] = sample.tach_drum
    self.data[0, 3] = sample.temp
    self.data[0, 3] = sample.pres       
    
  def get_by_label(self, label):
    labels = {"Time":0, "Pump rpm":1, "Drum rpm":2, "Temperature":3, "Pressure":4}
    print "Getting label %s" % label
    print " ...%s" % labels[label]
    print " ...(%d, %d)" %  (self.sample_data.shape[0], self.sample_data.shape[1])
    return self.sample_data[:,labels[label]]
      
  def load(self, log_no):
    self.reset()
    fn = "./log/record_%03d.npz" % log_no
    print "Load " + fn
    
    
    d = np.load(fn)     
    self.sample_data = np.zeros((len(d["samples"]),1), dtype=vs.Sample.data_type)
    
    i = 0
    for s in d["samples"]:
      print "Index i=%d " % (i)
      print s[1]
      self.sample_data[i]["time"]       = np.uint32(s[0]*1000)  # time
      self.sample_data[i]["tach_pump"]  = np.uint8(s[1])  # pump speed
      self.sample_data[i]["tach_drum"]  = np.uint8(s[2])  # drum speed
      self.sample_data[i]["temp"]       = np.int16(s[3])  # temperature
      self.sample_data[i]["pres"]       = np.int16(s[4])  # pressure
      
      sam = Sample()
      sam.Parse(vs.MODE.NOMODE, self.sample_data[i])
      self.add(sam)

      i += 1
      
      
  def reset(self):
    self.length = 0
    self.data = np.zeros((self.size, 5)) 
     
              
  def save(self, params):
    self.recordno += 1 
    s = "%s_%03d" % (log_filename, self.recordno)
    
    parameters = np.array([])
    for i in params:
      parameters = np.append(parameters, params[i].data, 0)
    np.savez(s, samples=self.data[0:self.length-1,:], parameters=parameters)                
                

if __name__ == '__main__':
  if len(sys.argv)>1:
    vs.Comm.SIMULATE = True

  app = wx.App(False)
  app.frame = GUIFrame()
  app.frame.Show()
  app.MainLoop()

